Creating and Drawing Bitmaps
QuickDraw GX provides a number of methods to create and draw bitmaps. For example, you can
The next section, "Creating Black-and-White Bitmaps," and "Creating Color Bitmaps," which begins on page 5-21, show you how to create bitmaps by specifying the bitmap geometry yourself.
- define a bitmap geometry and draw it without creating a bitmap shape
- define a bitmap geometry, encapsulate it in a bitmap shape, and draw the bitmap shape
- create another type of shape, convert it to a bitmap shape, perform any desired bitmap editing, and draw the bitmap shape
- create an offscreen bitmap, draw shapes to it, and then copy the offscreen bitmap to the screen
- unflatten a bitmap shape that was created earlier and stored to disk or that was created by another application
- convert a QuickDraw bitmap to a QuickDraw GX bitmap shape
The section "Converting Other Types of Shapes to Bitmaps," which begins on page 5-34, shows you how you can create a bitmap shape containing a bitmap representation of other types of QuickDraw GX shapes.
The section "Creating Bitmaps Offscreen," which begins on page 5-45, shows you how you can draw other shapes into the pixel image of a bitmap shape. You can use this method to create a bitmap representation of multiple QuickDraw GX shapes.
For information about flattening and unflattening bitmap shapes, see the chapter "Shape Objects" in Inside Macintosh: QuickDraw GX Objects.
Creating Black-and-White Bitmaps
You create a black-and-white bitmap by creating a bitmap shape with a pixel size of 1. To do this, you can define a pixel image, fill the fields of a bitmap geometry structure, and create a bitmap shape using theGXNewBitmap
function.Listing 5-1 shows a complete sample function that defines a black-and-white bitmap geometry, creates a bitmap shape, draws the shape, and disposes of it.
Listing 5-1 Creating a black-and-white bitmap
void CreateBlackAndWhiteBitmap(void) { gxShape aBitmapShape; gxBitmap aBitmapGeometry; gxPoint initialPosition = {ff(20), ff(40)}; const char envelopeImage[] = {0x7F, 0xFF, 0xFF, 0xFE, 0xC0, 0x00, 0x00, 0x03, 0xB0, 0x00, 0x00, 0x0D, 0x8C, 0x00, 0x00, 0x31, 0x83, 0x00, 0x00, 0xC1, 0x80, 0xC0, 0x03, 0x01, 0x80, 0x30, 0x0C, 0x01, 0x80, 0x0C, 0x30, 0x01, 0x80, 0x33, 0xCC, 0x01, 0x80, 0xC0, 0x03, 0x01, 0x83, 0x00, 0x00, 0xC1, 0x8C, 0x00, 0x00, 0x31, 0xB0, 0x00, 0x00, 0x0D, 0x7F, 0xFF, 0xFF, 0xFE}; aBitmapGeometry.image = (char *) aSmallBitmapImage; aBitmapGeometry.width = 32; /* width in pixels */ aBitmapGeometry.height = 14; /* height in pixels */ aBitmapGeometry.rowBytes = 4; /* bytes per row */ aBitmapGeometry.pixelSize = 1; /* bits per pixel */ /* QuickDraw GX creates a black-and-white color set for you */ aBitmapGeometry.space = gxNoSpace; aBitmapGeometry.set = nil; aBitmapGeometry.profile = nil; aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition); GXDrawShape(aBitmapShape); GXDisposeShape(aBitmapShape); }The result of this function is shown in Figure 5-8.Figure 5-8 A black-and-white bitmap--32 bits wide
The sample function from Listing 5-1 first defines a variable to hold the reference to the bitmap shape:
gxShape aBitmapShape;Then the sample function defines two local variables to specify the bitmap geometry:
gxBitmap aBitmapGeometry; gxPoint initialPosition = {ff(20), ff(40)};TheinitialPosition
variable, which is typegxPoint
, contains the initial bitmap position, and theaBitmapGeometry
variable, which is typegxBitmap
, contains the rest of the information about the bitmap.The sample function then defines the bitmap's pixel image:
const char envelopeImage[] = {0x7F, 0xFF, 0xFF, 0xFE, 0xC0, 0x00, 0x00, 0x03, 0xB0, 0x00, 0x00, 0x0D, 0x8C, 0x00, 0x00, 0x31, 0x83, 0x00, 0x00, 0xC1, 0x80, 0xC0, 0x03, 0x01, 0x80, 0x30, 0x0C, 0x01, 0x80, 0x0C, 0x30, 0x01, 0x80, 0x33, 0xCC, 0x01, 0x80, 0xC0, 0x03, 0x01, 0x83, 0x00, 0x00, 0xC1, 0x8C, 0x00, 0x00, 0x31, 0xB0, 0x00, 0x00, 0x0D, 0x7F, 0xFF, 0xFF, 0xFE};TheenvelopeImage
variable, which is defined as an array of bytes, contains a pixel image depicting a small envelope, as shown in Figure 5-8.To create a bitmap shape encapsulating this envelope image, the sample function fills in the eight fields of the
aBitmapGeometry
variable. First, it sets theimage
field by casting theenvelopeImage
variable to the correct type:
aBitmapGeometry.image = (char *) envelopeImage;Then the sample function fills in the bitmap dimensions. The bitmap is 32 pixels wide by 14 pixels high, and there are 4 bytes of information in each row of the pixel image:
aBitmapGeometry.width = 32; /* width in pixels */ aBitmapGeometry.height = 14; /* height in pixels */ aBitmapGeometry.rowBytes = 4; /* bytes per row */The sample function specifies the pixel size next. Since this bitmap is black-and-white, only one bit is needed to represent each pixel of the bitmap:
aBitmapGeometry.pixelSize = 1; /* bits per pixel */Finally, the sample function specifies color information. Since QuickDraw GX does not provide a black-and-white color space, this bitmap needs a black-and-white color set in which pixel values of 0 represent white pixels and pixel values of 1 represent black pixels. Setting thepixelSize
field to 1 and thespace
field togxNoSpace
indicates that QuickDraw GX should create this black-and-white color set for you.
aBitmapGeometry.space = gxNoSpace; aBitmapGeometry.set = nil; aBitmapGeometry.profile = nil;Setting thespace
field to the valuegxNoSpace
always indicates that QuickDraw GX should choose a color space for you. If the pixel size were large--for example, 16 or 32--QuickDraw GX would choose an RGB color space. However, since the pixel size is 1, no appropriate color space exists, so QuickDraw GX creates a grayscale color set. The pixel size determines the size of the color set created. In this case, a pixel size of 1 dictates that the color set have two entries--an white entry for a pixel value of 0 and a black entry for a pixel value of 1.After you define a bitmap geometry, you could use the
GXDrawBitmap
function to cause QuickDraw GX to
with this line of code:
- create a temporary bitmap shape (using the style, ink, and transform objects of the default bitmap shape)
- draw the bitmap
- dispose of the temporary bitmap shape
GXDrawBitmap(&aBitmapGeometry, &initialPosition);You should use theGXDrawBitmap
function, however, only when you know in advance that you want to draw a bitmap only one time.If you want to draw a bitmap more than once, you should encapsulate the bitmap geometry in a bitmap shape and then draw the bitmap shape. The sample function in Listing 5-1 uses this method:
aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition); GXDrawShape(aBitmapShape);As with any type of QuickDraw GX shape, if you create a bitmap shape, you are responsible for disposing of it when you no longer need it. Listing 5-1 does this by calling
GXDisposeShape(aBitmapShape);Notice that the envelope bitmap requires 4 bytes--an even number--to represent each row of the pixel image. However, to draw a similar envelope bitmap that includes two more rows of bits, as shown in Figure 5-10, the required number of bytes might seem to be 5 since 5 bytes contain 40 bits, more than enough needed to store the 34 bits per row in this image.However, if you set the
rowBytes
field to 5:
aBitmapGeometry.rowBytes = 5;both theGXDrawBitmap
function and theGXNewBitmap
function post the errorbitmap_rowBytes_not_aligned
, because the value of therowBytes
field must be an even number.Therefore, the value of the
rowBytes
field must be at least 6 for the bitmap of the envelope with a shadow. However, simply setting therowBytes
field to the value 6 with the assignment
aBitmapGeometry.rowBytes = 6;results in the bitmap shown in Figure 5-9.Figure 5-9 An example of unaligned bytes per row
Clearly, the value of the bitmap's
rowBytes
field is not aligned with the data in the bitmap's pixel image. If you set the value of therowBytes
field to 6, you must be sure to pad the pixel image so that each row actually contains 6 bytes of information. Listing 5-2 shows a new definition of the pixel image. In this definition, each row contains one extra byte so that the total number of bytes per row is even.In this example, the extra bytes are initialized to the value 0x00. However, since these bytes are just padding, you can specify any values for them. As indicated by the bitmap width, QuickDraw GX ignores these extra bytes when drawing, hit-testing, or otherwise manipulating the bitmap.
Listing 5-2 A bit image with an even number of bytes per row
static char envelopeImage[]= {0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x03, 0x50, 0x00, 0xB0, 0x00, 0x00, 0x0D, 0xA0, 0x00, 0x8C, 0x00, 0x00, 0x31, 0x50, 0x00, 0x83, 0x00, 0x00, 0xC1, 0xA0, 0x00, 0x80, 0xC0, 0x03, 0x01, 0x50, 0x00, 0x80, 0x30, 0x0C, 0x01, 0xA0, 0x00, 0x80, 0x0C, 0x30, 0x01, 0x50, 0x00, 0x80, 0x33, 0xCC, 0x01, 0xA0, 0x00, 0x80, 0xC0, 0x03, 0x01, 0x50, 0x00, 0x83, 0x00, 0x00, 0xC1, 0xA0, 0x00, 0x8C, 0x00, 0x00, 0x31, 0x50, 0x00, 0xB0, 0x00, 0x00, 0x0D, 0xA0, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x50, 0x00, 0x15, 0x55, 0x55, 0x55, 0xA0, 0x00, 0x0A, 0xAA, 0xAA, 0xAA, 0x80, 0x00};With this new, padded definition of the pixel image, you can setrowBytes
field to 6 so that the resulting bitmap appears as shown in Figure 5-10.Figure 5-10 An envelope with a shadow
For a discussion of pixel images and bitmap geometries, see "Bitmap Geometries" beginning on page 5-5.
For more information about the
GXNewBitmap
function, see its description on
page 5-66. For more information about theGXDrawBitmap
function, see its description on page 5-79.The next section shows you how you can create a bitmap with color information.
Creating Color Bitmaps
All QuickDraw GX bitmaps are actually color bitmaps. A black-and-white bitmap is simply a color bitmap with a color set containing only two colors--black and white.The sample function in Listing 5-1 on page 5-15 creates a black-and-white bitmap geometry by
The sample function encapsulates the geometry into a bitmap shape with this call to the
- specifying the pixel size to be 1
- specifying the color space to be the
gxNoSpace
color space
GXNewBitmap
function:
aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);Because thespace
field of the bitmap geometry specifies thegxNoSpace
color space, theGXNewBitmap
function chooses a color space for you, based on the pixel size specified in thepixelSize
field. QuickDraw GX does not provide any color spaces appropriate for a pixel size of 1, so theGXNewBitmap
function creates a grayscale color set with two entries--white and black.If you specify the
gxNoSpace
color space with a pixel size of 2, theGXNewBitmap
function creates a grayscale color set with four entries--white, light gray, dark gray, and black. After you change the pixel size to 2, you must reflect that change in the pixel image, the bitmap width, and the number of bytes per row.Typically, if you wanted to make a 1 bit-per-pixel bitmap into a 2 bit-per-pixel bitmap, you would do the following
This method allows you to maintain the size of the bitmap while allowing you to specify more possible values (colors) for each pixel.
- Maintain the bitmap width, as it represents the number of pixel values--not the number of bits--per row of the bitmap
- Double the number of bytes per row to accomodate the extra bits
- Double the size of the pixel image, replacing 1-bit pixel values with 2-bit pixel values
However, an easier (if somewhat less useful) way to make a 1 bit-per-pixel bitmap into a 2 bit-per-pixel bitmap is as follows:
Let's see what happens when you apply this simpler method for doubling the pixel size of a bitmap. Doubling the pixel size and halving the bitmap width of the envelope bitmap shown in Figure 5-10 on page 5-20 indicates that QuickDraw GX should interpret every pair of bits in the pixel image as a single pixel. Since each pixel can have one of four possible values (00, 01, 10, 11), the resulting bitmap contains four shades of gray, as shown in Figure 5-11.
- Divide the bitmap width in half
- Maintain the same number of bytes per row
- Maintain the same pixel image
Figure 5-11 A bitmap with a grayscale color set (four shades)
You can double the pixel size and halve the bitmap width again, with the following assignments:
aBitmapGeometry.width = 9; aBitmapGeometry.height = 16; aBitmapGeometry.rowBytes = 6; aBitmapGeometry.pixelSize = 4;QuickDraw GX interprets each set of 4 bits in the pixel image as representing a single pixel of the bitmap, which means each pixel can now be represented by 16 different values (0000, 0001, 0010, and so on). Since QuickDraw GX has no predefined color space that uses a pixel size of 4, it creates for this bitmap a color set with sixteen shades of gray.Figure 5-12 shows the resulting bitmap.
Figure 5-12 A bitmap with a grayscale color set (sixteen shades)
As the previous examples have shown, setting the
space
field of a bitmap geometry to thegxNoSpace
constant indicates that you want QuickDraw GX to choose a color space for you. In these examples, which had 1, 2, or 4 bits per pixel, QuickDraw GX chose thegxIndexedSpace
color space and created a grayscale color set with the appropriate number of color entries.You are not limited to these grayscale color sets, however. You can create your own color set, by choosing your own set of colors for the color entries. Listing 5-3 shows how to define a simple color set with eight colors--black and white, the three primary RGB colors, and the three secondary RGB colors.
Listing 5-3 Defining a color set
gxColorSet aColorSet; gxSetColor newColorList[] = { {0xFFFF, 0xFFFF, 0xFFFF, 0}, /* white */ {0xFFFF, 0, 0, 0}, /* red */ {0, 0xFFFF, 0, 0}, /* green */ {0, 0, 0xFFFF, 0}, /* blue */ {0, 0xFFFF, 0xFFFF, 0}, /* cyan */ {0xFFFF, 0, 0xFFFF, 0}, /* magenta */ {0xFFFF, 0xFFFF, 0, 0}, /* yellow */ {0, 0, 0, 0}, /* black */ };The colors in this color set are specified in the RGB color space, and each color contains four components--the red component, the green component, the blue component, and a fourth component, which QuickDraw GX ignores for the RGB color space. QuickDraw GX allows you to specify colors in other color spaces and with different numbers of components. For complete color-specifying information, see the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.Once you've defined the color list for a color set, you create the actual color set object by using the
GXNewColorSet
function, which requires you to specify the color space in which you've specified the colors, the total number of colors, and the list of colors:
aColorSet = GXNewColorSet(gxRGBSpace, 8, newColorList);To use the new color set in your bitmap, you need to set the
- Note
- Remember, you are responsible for disposing of QuickDraw GX objects when you no longer need them, so you are responsible for disposing of this new color set.
![]()
space
andset
fields of the bitmap geometry:
aBitmapGeometry.space = gxIndexedSpace; aBitmapGeometry.set = aColorSet;Setting thespace
field togxIndexedSpace
indicates that you are supplying the color set, rather than having QuickDraw GX create one for you.Figure 5-13 shows the result of applying this new color set to the 4-bits-per-pixel version of the envelope bitmap. Notice that pixel values in the pixel image greater than 7 are out of the range of the color set, so QuickDraw GX maps those pixel values to the color black.
For a color version of Figure 5-13, see Plate 3 at the front of this book.
Figure 5-13 A bitmap with an eight-color color set
Each of the previous examples in this chapter creates a bitmap that uses a color set. QuickDraw GX interprets each pixel of these bitmaps as an index into a set of colors. For example, in the black-and-white bitmap that results from Listing 5-1 on page 5-15, each pixel value (single bit) of the pixel image is an index into a color set with two colors--the index of the color white is 0 and the index of the color black is 1. In the 2 bits-per-pixel example on page 5-6, each pixel value (pair of bits) in the pixel image is an index into a color set with four colors--the index of white is 0 (bits 00), the index of light gray is 1 (bits 01), the index of dark gray is 2 (bits 10), and the index of black is 3 (bits 11).
QuickDraw GX also allows you to create bitmaps that use color spaces other than indexed color spaces (that is, other than color sets). In these bitmaps, each pixel value is an actual color value instead of an index into a list of colors. The chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects explains color spaces, color values, color sets, and color indexes.
One example of a bitmap for which you might want to use a color space instead of a color set is a color ramp. A color ramp is a shape that blends from one color into another. Since bitmaps are the only type of QuickDraw GX shape (except picture shapes) that allows multiple colors in one shape, you must implement color ramps as bitmap shapes.
The sample function in Listing 5-4 on page 5-26 shows how to create a simple color ramp. This function declares a bitmap shape reference and a bitmap geometry structure using the declarations
gxShape aBitmapShape; gxBitmap aBitmapGeometry;It then fills in the fields of the bitmap geometry structure. First, it fills in the dimensions:
aBitmapGeometry.width = 1; aBitmapGeometry.height = 256; aBitmapGeometry.rowBytes = 1; aBitmapGeometry.pixelSize = 32;Notice that the sample function defines the bitmap width to be 1. Later, the sample function uses theGXSetShapeBounds
function later to widen the bitmap.Next, the sample function sets the
image
field tonil
to indicate that QuickDraw GX should allocate memory for the pixel image of the bitmap. The value of therowBytes
field is ignored because QuickDraw GX sets this field when allocating the pixel image.The sample function then sets the color-related fields of the bitmap geometry structure:
aBitmapGeometry.space = gxRGB32Space; aBitmapGeometry.set = nil; aBitmapGeometry.profile = nil;Notice that the pixel size implied by the color space (which is thegxRGB32Space
color space) is the same as the pixel size indicated in thepixelSize
field of the bitmap geometry structure (which is 32).Next, the sample function creates the bitmap shape:
aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialPosition);The sample function sets the color values of each pixel in the bitmap shape. To do this, it creates a color structure with the declaration
gxColor current;Then it fills in the values of the fields of the color structure:
current.space = gxRGBSpace; current.profile = nil; current.element.rgb.red = 0xFFFF; current.element.rgb.green = 0; current.element.rgb.blue = 0; current.element.rgb.alpha = 0;For a complete discussion of these fields, see the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.The sample function then uses the
GXSetShapePixel
function to set each pixel value in the pixel image of the bitmap shape. Each time the sample function sets the value of a pixel, it changes the color value of thecurrent
variable slightly, decreasing the amount of green and increasing the amount of red:
for (count = 0; count < 256; count++) { current.element.component[0] -= 0x0101; /* decrease red */ current.element.component[1] += 0x0101; /* increase green */ GXSetShapePixel(aBitmapShape, 0, count, ¤t, 0); }Finally, the sample function resizes the bitmap, widening it to be a square, and draws the resulting bitmap color ramp. The complete sample function definition is shown in Listing 5-4.Listing 5-4 Creating a color ramp
void CreateColorRamp(void) { gxShape aBitmapShape; gxBitmap aBitmapGeometry; const gxPoint initialLocation = {ff(50), ff(50)}; const gxRectangle theBounds = {ff(50), ff(50), ff(150), ff(150)}; gxColor current; int count; /* create a one-pixel-wide bitmap */ aBitmapGeometry.width = 1; aBitmapGeometry.height = 256; aBitmapGeometry.rowBytes = 1; aBitmapGeometry.pixelSize = 32; aBitmapGeometry.image = nil; /* have QuickDraw GX allocate */ aBitmapGeometry.space = gxRGB32Space; aBitmapGeometry.set = nil; aBitmapGeometry.profile = nil; aBitmapShape = GXNewBitmap(&aBitmapGeometry, &initialLocation); /* create a red color */ current.space = gxRGBSpace; current.profile = nil; current.element.component[0] = 0xFFFF; /* red */ current.element.component[1] = 0; /* green */ current.element.component[2] = 0; /* blue */ current.element.component[3] = 0; /* alpha */ /* fill in the colors of the bitmap pixel by pixel */ for (count = 0; count < 256; count++) { current.element.rgb.red -= 0x0101; /* decrease red */ current.element.rgb.green += 0x0101; /* increase green */ GXSetShapePixel(aBitmapShape, 0, count, ¤t, 0); } /* resize the bitmap to give it more width */ GXSetShapeBounds(aBitmapShape, &theBounds); GXDrawShape(aBitmapShape); GXDisposeShape(aBitmapShape); }The resulting color ramp is shown in Figure 5-14. For a color version of this figure, see Plate 4 at the front of this book.Figure 5-14 A color ramp from red to green
QuickDraw GX provides the ramp library to assist you in creating color ramps. The
NewRamp
library function requires you to provide a start color, an end color, an integer indicating the number of different colors to calculate in between the start color and end color, and a bounding rectangle for the final color ramp. Listing 5-5 shows how to use this function to create the same color ramp shown in Figure 5-14.Listing 5-5 Creating a color ramp using the ramp library
void CreateColorRamp(void) { gxShape aBitmapShape; gxColor start, end; const gxRectangle theBounds = {ff(50), ff(50), ff(150), ff(150)}; start.space = gxRGBSpace; start.profile = nil; start.element.rgb.red = 0xFFFF; start.element.rgb.green = 0; start.element.rgb.blue = 0; start.element.rgb.alpha = 0; end.space = gxRGBSpace; end.profile = nil; end.element.rgb.red = 0; end.element.rgb.green = 0xFFFF; end.element.rgb.blue = 0; end.element.rgb.alpha = 0; aBitmapShape = NewRamp(&start, &end, 256, &theBounds); GXDrawShape(aBitmapShape); GXDisposeShape(aBitmapShape); }As a further convenience, QuickDraw GX provides the color library, which allows you to use predefined constants to specify frequently used colors. You provide theSetCommonColor
library function with a pointer to a color structure, and a predefined constant specifying the color you want.This function then initializes the color structure with the appropriate values to represent the color you specify.
For example, the following call sets the fields of the
start
color structure with the values that represent the color red:
SetCommonColor(&start, red);Listing 5-6 shows you how to create the color ramp in Figure 5-14 by using functions from both the ramp and color libraries.Listing 5-6 Creating a color ramp using both the ramp and color libraries
void CreateColorRamp(void) { gxShape aBitmapShape; gxColor start, end; const gxRectangle theBounds = {ff(50), ff(50), ff(150), ff(150)}; SetCommonColor(&start, red); SetCommonColor(&end, green); aBitmapShape = NewRamp(&start, &end, 0, &theBounds); GXDrawShape(aBitmapShape); GXDisposeShape(aBitmapShape); }For a discussion of pixel images and bitmap geometries, see "Bitmap Geometries" beginning on page 5-5.You can find more information about colors, color structures, color values, color sets, and color spaces in the chapter "Colors and Color-Related Objects" in Inside Macintosh: QuickDraw GX Objects.
Main | Page One | What's New | Apple Computer, Inc. | Find It | Contact Us | Help